/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package br.gov.cemaden.agent;

import br.gov.cemaden.agent.exception.ArgumentsException;
import br.gov.cemaden.agent.util.HibernateUtil;
import br.gov.cemaden.library.dao.produto.ProdutoDao;
import br.gov.cemaden.library.dao.produto.ProdutoItemDao;
import br.gov.cemaden.library.dao.smp.LogProdutoDao;
import br.gov.cemaden.library.model.Usuario;
import br.gov.cemaden.library.model.produto.Produto;
import br.gov.cemaden.library.model.produto.ProdutoItem;
import br.gov.cemaden.library.model.produto.Produtodescricao;
import br.gov.cemaden.library.model.smp.LogProduto;
import br.gov.cemaden.sendmail.Sendmail;
import br.gov.cemaden.sendmail.exception.SendmailConfigurationException;
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.mail.MessagingException;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author alan
 */
public class ProdutoMonitor extends Agent{
    
    private Path file;
    private final Map<Long, Long> sentmails;
    private Long executionPeriod, emailPeriod;
    private Long databaseExceptionPeriod;
    
    private final Logger logger;
    
    public ProdutoMonitor(){
        
        sentmails = new HashMap<Long, Long>();
        
        // executionPeriod default value is in milliseconds; it is received in seconds and validateArgs method parses the received velue to milliseconds
        executionPeriod = 60 * 1000L;
        // emailPeriod default value is in milliseconds; it is received in seconds and validateArgs method parses the received value to milliseconds
        emailPeriod = 10 * 60 * 1000L;
        // databaseExceptionPeriod = milliseconds
        databaseExceptionPeriod = (System.currentTimeMillis() - emailPeriod);
                
        this.logger = LoggerFactory.getLogger(ProdutoMonitor.class);
    }    
    
    @Override
    public void setup(){
        
        System.out.println("Starting agent " + getAID().getName());
        logger.info("Starting agent " + getAID().getName());

        Object[] args = getArguments();
        if (args != null) {
            validateArgs(args);
        } else {
            throw new ArgumentsException("The agent is expecting at least 1 (one) parameter to run properly.");
        }
     
        addBehaviour(new TickerBehaviour(this, executionPeriod) {
            
            @Override
            protected void onTick() {
                
                logger.debug(
                        String.format("Starting %s for the %d time at %s.", 
                        ProdutoMonitor.class.getName(), getTickCount(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()))
                );
                
                Session session = null;
                String exception = null;
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                
                // Tenta abrir a conexão com o banco de dados; em caso de erro, tenta enviar e-mail ao usuário cadastrado

                try {
                    session = HibernateUtil.getSessionFactory().openSession();
                } catch (HibernateException ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } catch (ExceptionInInitializerError ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } catch (Throwable ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } finally {
                    if (session == null) {
                        if (TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - databaseExceptionPeriod) >= emailPeriod){
                            String warning = "Agente: Erro durante a conexão com o banco de dados!!";
                            String body = String.format("%s%nO Agente %s ira tentar conectar-se novamente em %d segundos. "
                                    + "Caso não seja possível, um novo e-mail será enviado ao endereço de e-mail cadastrado.%n%n%s",
                                    warning, getAID().getName(), emailPeriod, exception);
                            
                            try {
                                new Sendmail(warning, body, getFile()).send(); 
                            } catch (MessagingException ex) {
                                logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                            } catch (SendmailConfigurationException ex) {
                                logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                            } catch (FileNotFoundException ex) {
                                logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                            } catch (IOException ex) {
                                logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                            }
                            
                            // Salva a data/hora de envio da exceção, em milisegundos.
                            databaseExceptionPeriod = System.currentTimeMillis();                            
                        }
                    }
                }
                
                if(session != null){
                    
                    try{
                    
                        ProdutoDao produtoDao = new ProdutoDao(session);
                        ProdutoItemDao itemDao = new ProdutoItemDao(session);
                    
                        List<Produto> produtos = produtoDao.findAll();
                    
                        for (Produto produto : produtos) {
                            // Pega o último objeto ProdutoItem dado o produto
                            ProdutoItem item = itemDao.findLastItemByProduto(produto);
                            // Se o produto não possuir nenhum item registrado, passa para o próximo produto
                            if(item == null)
                                continue;                            
                            // Pega o valor de datahoraItem do item
                            Date datahora = item.getDatahoraItem();                                                        
                            // Se o valor de datahoraCadastro for menor que a hora atual menos o valor, em minutos, definido para a frequência
                            if(datahora.getTime() < ( System.currentTimeMillis() - (produto.getFrequencia() * 60 * 1000) ) ){                                                            
                            
                                Produtodescricao descricao = produto.getDescricao();
                                
                                String subject = String.format("Erro de produto %s", descricao.getNome());
                                String body = String.format("O produto %s não está gerando dados desde %s", 
                                        descricao.getNome(), 
                                        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(datahora));
                                
                                Set<Usuario> usuarios = new HashSet<Usuario>();
                                Set<String> emails = new HashSet<String>();
                                for (Usuario usuario : descricao.getUsuarios()) {
                                    if(usuario.getEmail() != null)
                                        emails.add(usuario.getEmail());
                                }
                                    
                                // Se Map<Long, Long> sentmails não possuir um produto, inclua-o.
                                if (!sentmails.containsKey(produto.getId())) {
                                    sentmails.put(produto.getId(), (System.currentTimeMillis() - (emailPeriod * 1000L)));
                                }

                                // Se o período para o envio de e-mail expirou
                                if( (System.currentTimeMillis() - sentmails.get(produto.getId())) >= emailPeriod){
                                   
                                    try {
                                        if(emails.size() > 0){
                                            new Sendmail(subject, body, file)
                                                .setRecipients(emails)
                                                .send();
                                        }
                                    } catch (IOException ex) {
                                        ex.printStackTrace(pw);
                                        exception = sw.toString();
                                        body = String.format("%s%n%s", body, exception);
                                    } catch (MessagingException ex) {
                                        ex.printStackTrace(pw);
                                        exception = sw.toString();
                                        body = String.format("%s%n%s", body, exception);
                                    } catch (SendmailConfigurationException ex) {
                                        ex.printStackTrace(pw);
                                        exception = sw.toString();
                                        body = String.format("%s%n%s", body, exception);
                                    } finally {                                        
                                        
                                        // Salva o log com a notificação                                        
                                        LogProduto log = new LogProduto(produto, body, new Date(), usuarios);
                                        new LogProdutoDao(session).insert(log);
                                    }
                                } // #Fim - se a datahora da última notificação é maior que o desejado
                            } // #Fim - se a datahora da última aquisição for menor que a desejada                            
                        } // Fecha a conexão com o banco de dados
                        session.close();
                    } 
                    catch(HibernateException e){
                        e.printStackTrace(pw);
                        exception = sw.toString();
                        try {
                            new Sendmail("Exception when trying to execute ProdutoMonitor", exception, file).send();
                        } catch (IOException ex) {
                            logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                        } catch (MessagingException ex) {
                            logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                        } catch (SendmailConfigurationException ex) {
                            logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                        }
                    }                    
                } // #Fim - if ( session != null )                
            }
        });
        
    }
    
    @Override
    protected void takeDown(){
        System.out.println("Finishing agent " + getAID().getName());
    }
    
    private void setFile(String filename) throws FileNotFoundException {
        this.file = Paths.get(filename);
        if (Files.notExists(file)) {
            throw new FileNotFoundException("File not found: " + file);
        }
    }

    private Path getFile() {
        return this.file;
    }
    
    private void validateArgs(Object[] args) {
        if (args[0] != null) {
            try {
                this.setFile(args[0].toString());
            } catch (FileNotFoundException ex) {
                throw new ArgumentsException("The first argument " + args[0].toString() + " was not found.", ex);
            }
        } else {
            throw new ArgumentsException("There is no parameter for the e-mail properties file.");
        }
        if (args.length > 1) {
            executionPeriod = (args[1] != null) ? Long.parseLong(args[1].toString()) * 1000 : executionPeriod;
            if (args.length > 2) {
                if(args[2] != null){
                    Integer seconds = Integer.parseInt(args[2].toString());
                    if(seconds > TimeUnit.MILLISECONDS.toSeconds(emailPeriod)){
                        emailPeriod = seconds * 1000L;
                    }
                    else{
                        logger.error("The value of the third parameter (email period) was changed to " + TimeUnit.MILLISECONDS.toSeconds(emailPeriod) + " seconds.");
                    }
                }
            }
        }
    }
    
}
